#ifndef ___DERIVATIVE
#define ___DERIVATIVE

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "funct.h"

#ifdef unix
#include <pthread.h>
#else
#include <Windows.h>
#include <process.h>
#endif

/**
 * ToDo
 * 	Must test cloneTree build a new tree base on existed tree
 * */
 
void* derivative(void *);
TNode* d_product(TNode *t, TNode *u, TNode *du, TNode *v, TNode *dv, char x);
TNode* d_quotient(TNode *t, char x);
TNode* d_sum_subtract(TNode *t, char optr, TNode *u, TNode *du, TNode *v, TNode *dv, char x);
TNode* d_pow(TNode *t, TNode *u, TNode *du, TNode *v, TNode *dv, char x);
TNode* d_sin(TNode *t, TNode *u, TNode *du, TNode *v, TNode *dv, char x);
TNode* d_cos(TNode *t, TNode *u, TNode *du, TNode *v, TNode *dv, char x);
int isContainVar(TNode *t, char x);

TNode *createTreeNode(){
	TNode *p = (TNode*)malloc(sizeof(TNode));
	p->valueType = TYPE_FLOATING_POINT;
	p->value = 0.0f;
	(p->frValue).numerator = 0;
	(p->frValue).denomerator = 1;
	p->sign = 1;
	p->parent = p->left = p->right;
	return p;
}

TNode * cloneTree(TNode *t, TNode *cloneParent){
	TNode *c;
	
	if(t == NULL)
		return NULL;
	
	c = (TNode*)malloc(sizeof(TNode));
	memcpy(c, t, sizeof(TNode));
	c->parent = cloneParent;
	c->left = cloneTree(t->left, c);
	c->right = cloneTree(t->right, c);
	return c;
}
#ifdef unix
void* derivative(void *p){
#else
void* __stdcall derivative(void *p){
#endif
	DParam *dp = (DParam*)p;
	TNode *t = dp->t;
	char x = dp->x;
	TNode *u, *du, *v, *dv;
	TNode *r;
	
#ifdef unix
	pthread_t tdu, tdv;
	int id_du = -1, id_dv = -1;
#else
	HANDLE tdu, tdv;
#endif
	DParam pdu, pdv;
	
	if(t==NULL)
		return NULL;
	
	if(t->function == F_COE || t->function == F_CONSTAN || t->function == F_VAR){
		/* Normally, it never get here */
		return NULL;
	}	
	dv = du = NULL;
	
	u = t->left;
	v = t->right;
	

#ifdef unix
	if( (u!=NULL) && ((u->function == F_FUNCT) || (u->function == F_OPT)) ){
		pdu.t = t->left;
		pdu.x = x;
		id_du = pthread_create(&tdu, NULL, derivative, (void*)(&pdu));
	}
	
	if( (v != NULL) && ((v->function == F_FUNCT) || (v->function == F_OPT)) ){
		pdv.t = t->right;
		pdv.x = x;
		id_dv = pthread_create(&tdv, NULL, derivative, (void*)(&pdv));
	}
	if(id_du == 0)
		pthread_join(tdu, (void**)&du);
	if(id_dv == 0)
		pthread_join(tdv, (void**)&dv);
#else
	if( (u!=NULL) && ((u->function == F_FUNCT) || (u->function == F_OPT)) ){
		pdu.t = t->left;
		pdu.x = x;
		_beginthreadex(NULL, 0, &derivative, (void*)&pdv, 0, NULL);
		du = (TNode*) derivative(&pdu);
	}
	
	if( (v != NULL) && ((v->function == F_FUNCT) || (v->function == F_OPT)) ){
		pdv.t = t->right;
		pdv.x = x;
		dv = (TNode*) derivative(&pdv);
	}
#endif
	
	if(t->function == F_FUNCT){
		switch(t->chr){
			case SIN:
				return d_sin(t, u, du, v, dv, x);
				
			case COS:
				return d_cos(t, u, du, v, dv, x);
				
			case POW:
				return d_pow(t, u, du, v, dv, x);
		}
	}else if(t->function == F_OPT){
		switch(t->chr){
			case PLUS:
			case MINUS:
				return d_sum_subtract(t, t->chr, u, du, v, dv, x);
			break;
			
			case MUL:
				return d_product(t, u, du, v, dv, x);
			break;
		}
	}
	
	return NULL;
}

TNode* d_product(TNode *t, TNode *u, TNode *du, TNode *v, TNode *dv, char x){
	TNode *r;
	if( (v->function==F_COE || v->function==F_CONSTAN ) && 
		(u->function==F_COE || u->function==F_CONSTAN )){
		/* (value * value)' */
		r = (TNode *)malloc(sizeof(TNode));
		r->function = F_COE;
		r->value = 0.0;
		r->parent = NULL;
		r->left = r->right = NULL;
		return r;
	}else if( ((v->function==F_COE || v->function==F_CONSTAN ) &&  (u->function==F_VAR) ) ||
			((u->function==F_COE || u->function==F_CONSTAN ) &&  (v->function==F_VAR) )){
		/* (x * value)' OR (value * x)' */
		
		r = (TNode *)malloc(sizeof(TNode));
		r->parent = NULL;
		r->left = r->right = NULL;
		
		if(u->function==F_VAR){
			r->function = v->function;
			r->value = v->value;
			r->chr = v->chr;
		}else if(v->function==F_VAR){
			r->function = u->function;
			r->value = u->value;
			r->chr = u->chr;
		}
		/*printf(" Dervative product (x * value)' OR (value * x)' \n");*/
		return r;
	}else if( (u->function==F_COE || u->function==F_CONSTAN ) && (v->function==F_FUNCT || v->function==F_OPT )  ){
		/* (value * subfunction | operator)' */
		/*printf("Derivative of Product of (value * subfunction | operator) \n");*/
		r = (TNode *)malloc(sizeof(TNode));
		r->function = F_OPT;
		r->chr = MUL;
		r->parent = NULL;
		r->left = cloneTree(u, r);
		r->right = dv;
		if(dv != NULL)
			dv->parent = r;
		return r;
	}
	return NULL;
}

TNode* d_quotient(TNode *t, char x){
	return NULL;
}

TNode* d_sum_subtract(TNode *t, char optr, TNode *u, TNode *du, TNode *v, TNode *dv, char x){
	TNode *r;
	
	if( (v->function==F_COE || v->function==F_CONSTAN ) && 
		(u->function==F_COE || u->function==F_CONSTAN )){
		/* value + value */
		/*printf(" Add dervative of SUM of value & value \n");*/
		r = (TNode *)malloc(sizeof(TNode));
		r->function = F_COE;
		r->value = 0.0;
		r->parent = NULL;
		r->left = r->right = NULL;
		return r;
	}else if( ((v->function==F_COE || v->function==F_CONSTAN ) &&  (u->function==F_VAR) ) ||
			((u->function==F_COE || u->function==F_CONSTAN ) &&  (v->function==F_VAR) )){
		/* (x + value) OR (value + x) */
		/*printf(" Add dervative of SUM of x & value OR value & x \n");*/
		r = (TNode *)malloc(sizeof(TNode));
		r->function = F_COE;
		r->value = 1.0;
		r->parent = NULL;
		r->left = r->right = NULL;
		return r;
	}else if( (u->function==F_VAR) && (v->function==F_VAR) ){
		/* (x + x) */
		/*printf(" Add dervative of SUM of x & x\n");*/
		r = (TNode *)malloc(sizeof(TNode));
		r->function = F_COE;
		r->value = 2.0;
		r->parent = NULL;
		r->left = r->right = NULL;
		return r;
	}else if( u->function == F_FUNCT && (v->function==F_COE || v->function==F_CONSTAN ) ){
		/* ( subfunction + value) */
		/*printf(" Add dervative of SUM of ( subfunction + value)\n");*/
		r = (TNode *)malloc(sizeof(TNode));
		r->parent = NULL;
		r->function = F_OPT;
		r->chr = optr;
		r->left = du;
		r->right = (TNode *)malloc(sizeof(TNode));
		(r->right)->function = F_COE;
		(r->right)->value = 0.0;
		(r->right)->parent = NULL;
		(r->right)->left = (r->right)->right = NULL;
		return r;
	}else if( (u->function==F_COE || u->function==F_CONSTAN ) && (v->function==F_FUNCT) ){
		/* ( value + subfunction) */
		r = (TNode *)malloc(sizeof(TNode));
		r->parent = NULL;
		return r;
	}else if( (u->function == F_FUNCT || u->function == F_OPT ) &&  
				(v->function==F_OPT||v->function==F_FUNCT) ){
		/* ( subfunction + Operator) */
		/*printf(" Add dervative of SUM of ( subfunction + Operator)\n");*/
		r = (TNode *)malloc(sizeof(TNode));
		r->parent = NULL;
		r->function = F_OPT;
		r->chr = optr;
		
		r->left = du;
		if(du != NULL)
			du->parent = r;
		r->right = dv;
		if(dv != NULL)
			dv->parent = r;
			
		return r;
	}
	return NULL;
}

TNode* d_pow(TNode *t, TNode *u, TNode *du, TNode *v, TNode *dv, char x){
	TNode *r;
	if((u->function == F_VAR) && ( v->function==F_COE || v->function==F_CONSTAN ) ){
		/* x^value */
		/*printf(" (x^value)' \n");*/
		r = (TNode *)malloc(sizeof(TNode));
		r->function = F_OPT;
		r->chr = MUL;
		r->value = 0.0;
		r->parent = NULL;
		
		/*printf(" Form derivative of Pow \n");*/
					
		r->left = (TNode *)malloc(sizeof(TNode));
		(r->left)->parent = r;
		(r->left)->function = F_COE;		
		(r->left)->value = v->value;
		(r->left)->left = (r->left)->right = NULL;
		
		/*printf(" Form derivative left of Pow \n");*/
		
		r->right = (TNode *)malloc(sizeof(TNode));
		(r->right)->parent = r;
		(r->right)->function = F_FUNCT;
		(r->right)->chr = POW;
		
		/*printf(" Form derivative right of Pow \n");*/
					
		(r->right)->left = (TNode *)malloc(sizeof(TNode));
		((r->right)->left)->function = F_VAR;
		((r->right)->left)->chr = x;
		((r->right)->left)->parent = (r->right);
		((r->right)->left)->left = ((r->right)->left)->right = NULL;
		
		/*printf(" Form derivative right of Pow  4\n");*/
					
		(r->right)->right = (TNode *)malloc(sizeof(TNode));
		((r->right)->right)->function = F_COE;
		((r->right)->right)->value = v->value - 1.0;
		((r->right)->right)->parent = (r->right);
		((r->right)->right)->left = ((r->right)->right)->right = NULL;
		
		/*printf(" Form derivative right of Pow  5\n");*/
		
		return r;
	}else if((u->function == F_OPT) && ( v->function==F_COE || v->function==F_CONSTAN ) ){
		/* operator ^ value */
		r = (TNode *)malloc(sizeof(TNode));
		r->function = F_OPT;
		r->chr = MUL;
		r->value = 0.0;
		r->parent = NULL;
		
		/*printf(" Add * \n");*/
		
		r->left = (TNode *)malloc(sizeof(TNode));
		(r->left)->parent = r;
		(r->left)->function = F_OPT;
		(r->left)->chr = MUL;
		
		/*printf(" Add Left * \n");*/
		
		(r->left)->left = (TNode *)malloc(sizeof(TNode));
		((r->left)->left)->function = F_COE;
		((r->left)->left)->value = v->value;
		((r->left)->left)->parent = (r->left);
		((r->left)->left)->left = ((r->left)->left)->right = NULL;
		
		/*printf(" Add Left Value 1/(v-1) \n");*/
		
		(r->left)->right = (TNode *)malloc(sizeof(TNode));
		((r->left)->right)->function = F_OPT;
		((r->left)->right)->value = 0.0;
		((r->left)->right)->chr = POW;
		((r->left)->right)->parent = (r->left);
		
		/*printf(" Add Right POW \n");*/
		
		((r->left)->right)->left = cloneTree(u, ((r->left)->right));
		
		/*printf(" Add LEFT Copy U \n");*/
		
		((r->left)->right)->right = cloneTree(v, ((r->left)->right)); 
		
		
		(((r->left)->right)->right)->value = v->value - 1;
		/*printf(" Add Right v-1 \n");*/
		
		r->right = du;
		du->parent = r;
		/*printf(" Add Right du \n");*/
		return r;
	}if((u->function == F_FUNCT) && ( v->function==F_COE || v->function==F_CONSTAN ) ){
		/* (subfunction ^ value)' */
		r = (TNode *)malloc(sizeof(TNode));
		r->function = F_OPT;
		r->chr = MUL;
		r->value = 0.0;
		r->parent = NULL;
		
		/*printf(" Add * \n");*/
		
		r->left = (TNode *)malloc(sizeof(TNode));
		(r->left)->parent = r;
		(r->left)->function = F_OPT;
		(r->left)->chr = MUL;
		
		/*printf(" Add Left * \n");*/
		
		(r->left)->left = (TNode *)malloc(sizeof(TNode));
		((r->left)->left)->function = F_COE;
		((r->left)->left)->value = v->value;
		((r->left)->left)->parent = (r->left);
		((r->left)->left)->left = ((r->left)->left)->right = NULL;
		
		/*printf(" Add Left Value 1/(v-1) \n");*/
		
		(r->left)->right = (TNode *)malloc(sizeof(TNode));
		((r->left)->right)->function = F_OPT;
		((r->left)->right)->value = 0.0;
		((r->left)->right)->chr = POW;
		((r->left)->right)->parent = (r->left);
		
		/*printf(" Add Right POW \n");*/
		
		((r->left)->right)->left = cloneTree(u, ((r->left)->right));
		
		/*printf(" Add LEFT Copy U \n");*/
		
		((r->left)->right)->right = cloneTree(v, ((r->left)->right)); 
		
		
		(((r->left)->right)->right)->value = v->value - 1;
		/*printf(" Add Right v-1 \n");*/
		
		r->right = du;
		du->parent = r;
		/*printf(" Add Right du \n");*/
		
		return r;
	}
	
	return NULL;
}

TNode* d_sin(TNode *t, TNode *u, TNode *du, TNode *v, TNode *dv, char x){
	TNode *r;
	if( (t->right)->function == F_COE || (t->right)->function == F_CONSTAN ){
		/* (sin(value))' = (CONSTAN)' = 0 */
		r = (TNode *)malloc(sizeof(TNode));
		r->function = F_COE;
		r->value = 0.0;
		r->parent = NULL;
		r->left = r->right = NULL;
		return r;
	}else if( (t->right)->function == F_VAR){
		/* (sin(x))' = cos(x) */
		r = (TNode *)malloc(sizeof(TNode));
		r->function = F_FUNCT;
		r->chr = COS;
		r->parent = NULL;
		r->left = NULL;
		/*printf(" (sin(x))' = cos(x) \n");*/
		r->right = cloneTree(v, r);
		return r;
	}
	
	return NULL;
}

TNode* d_cos(TNode *t, TNode *u, TNode *du, TNode *v, TNode *dv, char x){
	TNode *r;
	if( (t->right)->function == F_COE || (t->right)->function == F_CONSTAN ){
		/* (cos(value))' = (CONSTAN)' = 0 */
		r = (TNode *)malloc(sizeof(TNode));
		r->function = F_COE;
		r->value = 0.0;
		r->parent = NULL;
		r->left = r->right = NULL;
		return r;
	}else if( (t->right)->function == F_VAR){
		/* (cos(x))' = -sin(x) */
		r = (TNode *)malloc(sizeof(TNode));
		r->function = F_FUNCT;
		r->chr = SIN; /* <== negative here */
		r->sign = -1;
		r->parent = NULL;
		r->left = NULL;
		/*printf(" (sin(x))' = cos(x) \n");*/
		r->right = cloneTree(v, r);
		return r;
	}
}

int isContainVar(TNode *t, char x){
	
	if( (t==NULL) || (t->function==F_COE) || (t->function==F_CONSTAN) )
		return 0;
		
	if( t->function == F_VAR ){
		if(t->chr == x)
			return 1;
		return 0;
	}
	
	return (isContainVar(t->left,x) || isContainVar(t->right, x) );
}

#endif
